package math.region;

import math.math;
import math.vec2;
import math.vec3;

public class Tri3 implements Surface3
{
    
    public vec3 mOrig;
    
    public vec3 mA;
    
    public vec3 mB;
    
    
    public Tri3()
    {
        // Instantiate all members:
        this.mOrig = new vec3();
        this.mA = new vec3();
        this.mB = new vec3();
        // Init the Rect3 to a default state:
        this.set(vec3.ZERO, vec3.AXIS_XP, vec3.AXIS_YP);
    }
    
    
    public final void set(
            vec3 aOrig, 
            vec3 aBasisA, 
            vec3 aBasisB)
    {
        this.mOrig.set(aOrig);
        this.mA.set(aBasisA);
        this.mB.set(aBasisB);
    }
    
    public final void set(Tri3 aOther)
    {
        this.set(aOther.mOrig, aOther.mA, aOther.mB);
    }
    
    
    public boolean onSurface(vec3 aPoint)
    {
        vec3 toPt = vec3.sub(aPoint, this.mOrig);
        double u = toPt.projFraction(this.mA);
        double v = toPt.projFraction(this.mB);
        vec3 norm = vec3.cross(this.mA, this.mB);
        norm.norm();
        double w = toPt.projFraction(norm);
        return (
            u >= 0.0 && u <= 1.0 && 
            v >= 0.0 && v <= 1.0 && 
            math.abs(w) < math.EPSILON
        );
    }
    
    
    public double surfaceArea()
    {
        return this.crossAB().length();
    }
    
    
    public vec3 crossAB()
    {
        return vec3.cross(this.mA, this.mB);
    }
    
    public vec3 crossBA()
    {
        return vec3.cross(this.mB, this.mA);
    }
    
    public vec3 surfaceNorm(
            vec3 aPoint, 
            vec3 aReferencePt)
    {
        vec3 localRefPt = vec3.sub(aReferencePt, this.mOrig);
        vec3 norm = this.crossAB();
        norm.norm();
        boolean onNormSide = localRefPt.dot(norm) >= 0.0;
        if (!onNormSide)
        {
            norm.mul(-1.0);
        }
        return norm;
    }
    
    public vec3 convertParam_World(vec2 aUV)
    {
        vec3 scaledBasisX = vec3.mul(this.mA, aUV.x);
        vec3 world =        vec3.mul(this.mB, aUV.y); // scaledBasisY
        world.add(scaledBasisX);
        world.add(this.mOrig);
        return world;
    }
    
    
    public vec2 convertWorld_Param(vec3 aPos)
    {
        vec3 toPt = vec3.sub(aPos, this.mOrig);
        return new vec2(
            toPt.projFraction(this.mA), 
            toPt.projFraction(this.mB)
        );
    }
    
    
    public Surface3 lerp(Surface3 next, double t)
    {
        Tri3 rnext = (Tri3) next;
        Tri3 lerp = this.copy();
        lerp.mOrig.lerp(rnext.mOrig, t);
        lerp.mA.lerp(rnext.mA, t);
        lerp.mB.lerp(rnext.mB, t);
        return lerp;
    }
    
    
    public AABB3 boundingBox()
    {
        AABB3 box = new AABB3();
        box.extendToContain(this.mOrig);
        box.extendToContain(vec3.add(this.mOrig, this.mA));
        box.extendToContain(vec3.add(this.mOrig, this.mB));
        box.extendToContain(vec3.add(this.mOrig, vec3.add(this.mA, this.mB)));
        return box;
    }
    
    
    public Tri3 copy()
    {
        Tri3 copy = new Tri3();
        copy.set(this);
        return copy;
    }
    
    
    public String toString()
    {
        StringBuilder sb = new StringBuilder();
        sb.append("Rect3\n   Orig: ");
        sb.append(this.mOrig);
        sb.append("\n   A: ");
        sb.append(this.mA);
        sb.append("\n   B: ");
        sb.append(this.mB);
        return sb.toString();
    }
    
}
